import { reactive, ref, toRefs } from "vue";
const dragHeight = ref(0);
let dragData = null;
let movingTo = null;
let dropRects = {};
let isMoving = false;

const calcArea = ({ x1, x2, y1, y2 }, { ox1, ox2, oy1, oy2 }) => {
  if (x1 >= ox2 || ox1 >= x2 || y2 <= oy1 || y1 >= oy2) {
    return 0;
  }
  let lx1 = Math.max(x1, ox1);
  let lx2 = Math.min(x2, ox2);
  let ly1 = Math.max(y1, oy1);
  let ly2 = Math.min(y2, oy2);
  return (lx2 - lx1) * (ly2 - ly1);
};
const checkMovingRect = (base) => {
  let max = 0;
  let dropRect = null;
  for (let item of Object.values(dropRects)) {
    const { el } = item;
    const { area } = checkArea(el.value, base);
    if (area > max && area > 10) {
      max = area;
      dropRect = item;
    }
  }
  return dropRect;
};
const checkMovingPos = (newLeft, newTop, width) => {
  const base = {
    ox1: newLeft,
    ox2: newLeft + width,
    oy1: newTop,
    oy2: newTop + 30,
  };
  const dropRect = checkMovingRect(base);
  if (dropRect) {
    // 要放入的区域
    let dropPos; // 要插入的位置
    let max = 0;
    const { id, el, dragOver } = dropRect;
    const areaList = el.value.children;
    for (let i = 0; i < areaList.length; i++) {
      const { pos, area } = checkArea(areaList[i], base);
      if (area > max && area > 5) {
        max = area;
        dropPos = i + (pos === "before" ? 0 : 1);
      }
    }
    if (!movingTo || movingTo.pos !== dropPos || movingTo.id !== id) {
      dragOver(dropPos);
      if (movingTo && movingTo.id !== id) {
        movingTo.dragLeave(dragData.data, movingTo.pos);
      }
      movingTo = { ...dropRect, pos: dropPos };
    }
  } else if (movingTo) {
    movingTo.dragLeave(dragData.data, movingTo.pos);
    movingTo = null;
  }
};
const checkArea = (dom, base) => {
  let { left, top } = dom.getBoundingClientRect();
  let { offsetWidth, offsetHeight } = dom;
  let x1 = left;
  let x2 = x1 + offsetWidth;
  let topY1 = top;
  let bottomY2 = topY1 + offsetHeight;
  let topY2 = (topY1 + bottomY2) / 2;
  let bottomY1 = topY2;
  let topArea = calcArea({ x1, x2, y1: topY1, y2: topY2 }, base);
  let bottomArea = calcArea({ x1, x2, y1: bottomY1, y2: bottomY2 }, base);
  return topArea > bottomArea
    ? { pos: "before", area: topArea }
    : { pos: "after", area: bottomArea };
};
const startDrag = function (
  event,
  target,
  initData // {id, pos, data}
) {
  dragData = initData;
  const { dragStart, dragEnd } = dropRects[initData.id];
  const initX = parseInt(event.clientX);
  const initY = parseInt(event.clientY);
  const { offsetWidth, offsetHeight } = target;
  dragHeight.value = offsetHeight;
  const { left, top } = target.getBoundingClientRect();
  dragStart(dragData.data, dragData.pos);
  const dom = target.cloneNode(true);
  dom.style.zIndex = "5";
  dom.style.position = "fixed";
  dom.style.left = `${left}px`;
  dom.style.top = `${top}px`;
  dom.style.width = `${offsetWidth}px`;
  dom.style.background = "white";
  document.body.appendChild(dom);
  const mouseMove = (me) => {
    isMoving = true;
    const newLeft = parseInt(me.clientX) - initX + left;
    const newTop = parseInt(me.clientY) - initY + top;
    dom.style.left = `${newLeft}px`;
    dom.style.top = `${newTop}px`;
    checkMovingPos(newLeft, newTop, offsetWidth, offsetHeight);
  };
  document.addEventListener("mousemove", mouseMove);

  document.addEventListener(
    "mouseup",
    () => {
      document.removeEventListener("mousemove", mouseMove);
      if (!dom) return;
      document.body.removeChild(dom);
      if (movingTo && dragData.id !== movingTo.id) {
        movingTo.drop(dragData.data, movingTo.pos);
      }
      const isStillOn = dragData.id === movingTo?.id || !isMoving;
      const insertPos = movingTo ? movingTo.pos : dragData.pos;
      dragEnd(isStillOn, insertPos, dragData.data);
      dragData = null;
      movingTo = null;
      isMoving = false;
    },
    { once: true }
  );
};
const dragStart = (state, list, item, index) => {
  state.isDragIn = true;
  state.dropPos = index;
  list.splice(index, 1);
};
const dragOver = (state, list, pos) => {
  state.isDragIn = true;
  if (pos >= 0 && pos <= list.length) {
    state.dropPos = pos;
  }
};
const dragLeave = (state, list) => {
  resetDragInfo(state, list);
};
const drop = (state, list, data, insertPos) => {
  list.splice(insertPos ?? list.length, 0, data);
  resetDragInfo(state, list);
};
const dragEnd = (state, list, isStillOn, insertPos, data) => {
  if (!isStillOn) return;
  list.splice(insertPos ?? list.length, 0, data);
  resetDragInfo(state, list);
};
const resetDragInfo = (state) => {
  state.isDragIn = false;
  state.dropPos = void 0;
};

export default function useDrag(id, list, options = {}) {
  const { dropAble = true } = options;

  const state = reactive({
    dropPos: void 0,
    isDragIn: false,
  });
  const el = ref(null);

  if (!dropAble) {
    return {
      state,
      el,
      onStartDrag: () => {},
      dragHeight,
    };
  }

  dropRects[id] = {
    el,
    id,
    dragStart: dragStart.bind(null, state, list),
    dragOver: dragOver.bind(null, state, list),
    dragLeave: dragLeave.bind(null, state, list),
    drop: drop.bind(null, state, list),
    dragEnd: dragEnd.bind(null, state, list),
  };

  const onStartDrag = (e, el, pos) => {
    const dragData = {
      id,
      pos,
      data: list[pos],
    };
    startDrag(e, el, dragData);
  };

  return {
    state: toRefs(state),
    el,
    onStartDrag,
    dragHeight,
  };
}
